/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <journal/models/db/ink_surface_table.h>
#include <journal/models/db/common_table.h>
#include <journal/app/constants.h>

using namespace std;
using namespace journal::model::db;

ink_surface_table::ink_surface_table()
{
}

std::shared_ptr<journal::model::db::iink_surface_table> ink_surface_table::create(std::shared_ptr<s::iengine> dbEngine)
{
    std::shared_ptr<journal::model::db::ink_surface_table> returnValue = make_shared<ink_surface_table>();
    returnValue->_commonTableClient = std::dynamic_pointer_cast<icommon_table_client>(returnValue);
    returnValue->_commonTable = createCommonTable(returnValue->_commonTableClient, dbEngine, L"InkSurface");
    return returnValue;
}

std::shared_ptr<icommon_table> ink_surface_table::common()
{
    return _commonTable;
}

void ink_surface_table::defineColumns(std::shared_ptr<s::itable> table)
{
    BAJA_LOCK(_mutex);

    _inkSurfaceIdColumn = s::createColumn<b::uint32>(L"ID", s::column_type::type_int32);
    table->addColumn(_inkSurfaceIdColumn);
    
    _pageIdColumn = s::createColumn<b::uint32>(L"PageID", s::column_type::type_int32);
    table->addColumn(_pageIdColumn);

    _inkStrokesSizeColumn = s::createColumn<b::uint32>(L"InkStrokesSize", s::column_type::type_int32);
    _inkStrokesSizeColumn->setIsBlobSize(true);
    table->addColumn(_inkStrokesSizeColumn);

    //_inkStrokesColumn = s::createColumn<s::binary_blob>(L"InkStrokes", s::column_type::type_binaryBlob);
    _inkStrokesColumn = s::createBlobColumn(L"InkStrokes", s::column_type::type_binaryBlob);
    _inkStrokesColumn->setFixed(false);
    _inkStrokesColumn->setMaxBufferSizeInBytes(4194304); // 4 MB
    table->addColumn(_inkStrokesColumn);
}

void ink_surface_table::defineIndexColumns(std::shared_ptr<s::iindex> primaryKeyIndex)
{
    BAJA_LOCK(_mutex);

    // parent column has to be added before self to facilitate searches with parent only.
    _pageIdIndexColumn = s::createIndexColumnDefinition(_pageIdColumn, true);
    primaryKeyIndex->addIndexColumn(_pageIdIndexColumn);

    _inkSurfaceIdIndexColumn = s::createIndexColumnDefinition(_inkSurfaceIdColumn, true);
    primaryKeyIndex->addIndexColumn(_inkSurfaceIdIndexColumn);
}

void ink_surface_table::defineRowCells(std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> row)
{
    BAJA_LOCK(_mutex);

    // Bug 322: TRACK: can we get rid of this 'cell' concept?
    _inkSurfaceIdCell = _inkSurfaceIdColumn->createCell(0);
    _pageIdCell = _pageIdColumn->createCell(0);
    _inkStrokesSizeCell = _inkStrokesSizeColumn->createCell(0);
    _inkStrokesCell = _inkStrokesColumn->createCellByMoving(s::binary_blob(0, nullptr));

    row->push_back(_inkSurfaceIdCell);
    row->push_back(_pageIdCell);
    row->push_back(_inkStrokesSizeCell);
    row->push_back(_inkStrokesCell);
}

void ink_surface_table::insert(b::uint32 inkSurfaceId,
                               b::uint32 pageId)
{
    BAJA_LOCK(_mutex);

    _inkSurfaceIdCell->setValue(inkSurfaceId);
    _pageIdCell->setValue(pageId);
    _inkStrokesSizeCell->setValue(0);
    _inkStrokesCell->setAppendOnUpdate(false);
    _inkStrokesCell->setValue(s::binary_blob());

    _commonTable->insert(_commonTable->stagingRow());
}

void ink_surface_table::appendInkStroke(
    b::uint32 inkSurfaceId,
    b::uint32 pageId,
    b::uint32 allStrokesSize,
    s::binary_blob& inBlob)
{
    BAJA_LOCK(_mutex);

    if (allStrokesSize > _inkStrokesCell->getColumn()->getMaxBufferSizeInBytes())
    {
        constants::uiService()->showDialog(L"New strokes will no longer be saved.");
        return;
    }
    _inkSurfaceIdCell->setValue(inkSurfaceId);
    _pageIdCell->setValue(pageId);
    _inkStrokesSizeCell->setValue(allStrokesSize);
    _inkStrokesCell->setAppendOnUpdate(true);
    _inkStrokesCell->setValueByMoving(inBlob);

    _commonTable->update(_commonTable->stagingRow(),
        static_cast<baja::uint32>(_commonTable->primaryIndex()->getIndexColumns()->size()));
}

void ink_surface_table::replaceInkStrokes(
    b::uint32 inkSurfaceId,
    b::uint32 pageId,
    s::binary_blob& inBlob)
{
    BAJA_LOCK(_mutex);

    if (inBlob._size > _inkStrokesCell->getColumn()->getMaxBufferSizeInBytes())
    {
        constants::uiService()->showDialog(L"New strokes will no longer be saved.");
        return;
    }
    _inkSurfaceIdCell->setValue(inkSurfaceId);
    _pageIdCell->setValue(pageId);
    _inkStrokesSizeCell->setValue(inBlob._size);
    _inkStrokesCell->setAppendOnUpdate(false);
    _inkStrokesCell->setValueByMoving(inBlob);

    _commonTable->update(_commonTable->stagingRow(),
        static_cast<baja::uint32>(_commonTable->primaryIndex()->getIndexColumns()->size()));
}

void ink_surface_table::load(
    unsigned int inkSurfaceId,
    unsigned int pageId)
{
    BAJA_LOCK(_mutex);

    // no other attributes to load.
}

void ink_surface_table::loadInkStrokes(
    b::uint32 inkSurfaceId,
    b::uint32 pageId,
    s::binary_blob& outBlob)
{
    BAJA_LOCK(_mutex);

    // select columns...
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(_inkSurfaceIdColumn);
    result->addColumn(_inkStrokesSizeColumn);
    result->addColumn(_inkStrokesColumn);

    // where table.id = id
    read(inkSurfaceId, pageId, result);

    // iterate through the result...
    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    BAJA_ASSERT(rows->size() <= 1, L"One (and only one) records is expected.");

    std::vector<std::shared_ptr<s::iresult_row>>::iterator rowIt;
    for (rowIt = rows->begin(); rowIt < rows->end(); ++rowIt)
    {
        // Bug 323 : TRACK: Gmileka: It'd be nice to have an easier way of getting directly to the cell we need...
        std::shared_ptr<s::iresult_row> row = *rowIt;
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();
        std::vector<std::shared_ptr<s::icell>>::iterator cellIt;
        // There should be one cell per row only...
        for (cellIt = cells->begin(); cellIt < cells->end(); ++cellIt)
        {
            std::shared_ptr<s::icell> cell = *cellIt;
            if (cell->getColumnId() == _pageIdColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                if (inkSurfaceId == typedCell->getValue())
                {
                    continue;
                }
                else
                {
                    BAJA_ASSERT(false, L"The query must return the requested row only!");
                }
            }
            if (cell->getColumnId() == _inkStrokesColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<s::binary_blob>> typedCell = std::dynamic_pointer_cast<s::icell_typed<s::binary_blob>>(cell);
                typedCell->getValueByMoving(outBlob);
            }
        }
    }
}

void ink_surface_table::read(b::uint32 inkSurfaceId,
                             b::uint32 pageId,
                             std::shared_ptr<s::iresult> result)
{
    BAJA_LOCK(_mutex);

    _inkSurfaceIdCell->setValue(inkSurfaceId);
    _pageIdCell->setValue(pageId);

    _commonTable->read(_commonTable->stagingRow(),
                       static_cast<baja::uint32>(_commonTable->primaryIndex()->getIndexColumns()->size()),
                       result);
}

void ink_surface_table::readIds(b::uint32 pageId,
                                std::vector<b::uint32>& ids)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);

    _commonTable->readIds(_commonTable->stagingRow(),
                          _inkSurfaceIdColumn,
                          ids);
}

void ink_surface_table::dispose(
    b::uint32 inkSurfaceId,
    b::uint32 pageId)
{
    BAJA_LOCK(_mutex);

    _inkSurfaceIdCell->setValue(inkSurfaceId);
    _pageIdCell->setValue(pageId);

    _commonTable->dispose(_commonTable->stagingRow());
}

void ink_surface_table::dispose(b::uint32 pageId)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);
    _commonTable->dbEngine()->dispose(
        _commonTable->tableDefinition(),
        _commonTable->primaryIndex(),
        _commonTable->stagingRow(),
        1,          // only take into account pageId
        false);
}

std::shared_ptr<journal::model::db::iink_surface_table> journal::model::db::createInkSurfaceTable(std::shared_ptr<s::iengine> dbEngine)
{
    return ink_surface_table::create(dbEngine);
}
